#!/bin/bash
# /**
# * Copyright (c) 2018, yzfcer@163.com
# * All rights reserved.
# * @file    rename_all.sh
# * @brief   重命名符合规则的文件
# * @author  Jason Zhou,yzfcer@163.com
# * @version 1.0
# * @date    2021-09-15
# */


# Function to display help information using heredoc
show_help() {
    cat <<EOF
Usage: $(basename "$0") <directory> [OPTIONS]
Batch rename directories or files by changing prefixes/middles/suffixes

Options:
  -d, --dir-mode          Rename directories (mutually exclusive with -f)
  -f, --filter EXTENSIONS Filter files by extension (e.g., "c|h|sh") (mutually exclusive with -d)
  -p, --prefix PREFIX     Current prefix to match and replace
  -m, --middle MIDDLE     Current middle part to match and replace (between prefix and suffix)
  -s, --suffix SUFFIX     Current suffix to match and replace (before extension for files)
  -n, --new NEW_PATTERN   New pattern to replace prefix/middle/suffix
  --dry-run               Show what would be changed without actually renaming
  -y, --yes               Skip confirmation prompt
  -h, --help              Display this help message

Examples:
  Rename directories:
    $(basename "$0") /path/to/dir -d -p sw -n sys
        # Changes directory prefix from 'sw' to 'sys'

  Rename files:
    $(basename "$0") /path/to/dir -f "c|h" -p sw -n sys
        # Changes prefix from 'sw' to 'sys' for .c and .h files

    $(basename "$0") /path/to/dir -f "sh" -s old -n new --dry-run
        # Changes suffix from 'old' to 'new' for .sh files (dry run)

    $(basename "$0") /path/to/dir -f "txt" -m part -n component
        # Changes middle 'part' to 'component' in .txt files

Note: The script shows changes first and asks for confirmation unless --yes or --dry-run is used.
EOF
}

# Initialize global variables
init_globals() {
    target_dir=""
    dir_mode=false
    file_filter=""
    current_prefix=""
    current_middle=""
    current_suffix=""
    new_pattern=""
    dry_run=false
    assume_yes=false
}

# Validate command line arguments
validate_arguments() {
    if [[ -z "$target_dir" ]]; then
        echo "Error: Target directory must be specified" >&2
        show_help >&2
        exit 1
    fi

    if [[ ! -d "$target_dir" ]]; then
        echo "Error: Directory '$target_dir' does not exist" >&2
        exit 1
    fi

    if [[ "$dir_mode" == true && -n "$file_filter" ]]; then
        echo "Error: -d and -f are mutually exclusive options" >&2
        show_help >&2
        exit 1
    fi

    if [[ "$dir_mode" == false && -z "$file_filter" ]]; then
        echo "Error: Must specify either -d for directories or -f for files" >&2
        show_help >&2
        exit 1
    fi

    if [[ -z "$current_prefix" && -z "$current_middle" && -z "$current_suffix" ]]; then
        echo "Error: Must specify at least one of -p/--prefix, -m/--middle or -s/--suffix" >&2
        show_help >&2
        exit 1
    fi

    if [[ -z "$new_pattern" ]]; then
        echo "Error: New pattern must be specified with -n/--new" >&2
        show_help >&2
        exit 1
    fi
}

# Build find command without depth limit and proper extension filtering
build_find_command() {
    local cmd=("find" "$target_dir")

    if [[ "$dir_mode" == true ]]; then
        cmd+=("-type" "d" "!" "-name" "$(basename "$target_dir")")
    else
        cmd+=("-type" "f")
        if [[ -n "$file_filter" ]]; then
            IFS='|' read -ra extensions <<< "$file_filter"
            if [[ ${#extensions[@]} -gt 1 ]]; then
                cmd+=("(")
            fi
            for ((i=0; i<${#extensions[@]}; i++)); do
                if [[ $i -gt 0 ]]; then
                    cmd+=("-o")
                fi
                cmd+=("-name" "*.${extensions[i]}")
            done
            if [[ ${#extensions[@]} -gt 1 ]]; then
                cmd+=(")")
            fi
        fi
    fi

    cmd+=("-print0")
    echo "${cmd[@]}"
}

# Process each item and determine new name
process_item() {
    local itemname="$1"
    local new_itemname="$itemname"
    local changed=false

    # For files, handle extension separately
    if [[ "$dir_mode" == false ]]; then
        local extension="${itemname##*.}"
        local basename="${itemname%.*}"
        new_itemname="$basename"
    fi

    # Check and modify prefix
    if [[ -n "$current_prefix" && "$new_itemname" == "$current_prefix"* ]]; then
        new_itemname="$new_pattern${new_itemname#$current_prefix}"
        changed=true
    fi

    # Check and modify middle
    if [[ -n "$current_middle" ]]; then
        local temp_name="$new_itemname"
        # Remove prefix if specified
        if [[ -n "$current_prefix" && "$temp_name" == "$current_prefix"* ]]; then
            temp_name="${temp_name#$current_prefix}"
        fi
        # Remove suffix if specified
        if [[ -n "$current_suffix" && "$temp_name" == *"$current_suffix" ]]; then
            temp_name="${temp_name%$current_suffix}"
        fi
        # Replace middle part
        if [[ "$temp_name" == *"$current_middle"* ]]; then
            temp_name="${temp_name//$current_middle/$new_pattern}"
            # Reconstruct full name
            new_itemname=""
            [[ -n "$current_prefix" && "$new_itemname" == "$current_prefix"* ]] && new_itemname+="$current_prefix"
            new_itemname+="$temp_name"
            [[ -n "$current_suffix" && "$new_itemname" != *"$current_suffix" ]] && new_itemname+="$current_suffix"
            changed=true
        fi
    fi

    # Check and modify suffix
    if [[ -n "$current_suffix" && "$new_itemname" == *"$current_suffix" ]]; then
        new_itemname="${new_itemname%$current_suffix}$new_pattern"
        changed=true
    fi

    # For files, add back the extension
    if [[ "$dir_mode" == false && "$changed" == true ]]; then
        new_itemname="${new_itemname}.${extension}"
    fi

    echo "$changed:$itemname:$new_itemname"
}

# Main renaming function
rename_items() {
    local find_cmd=($(build_find_command))
    local item_type=$([[ "$dir_mode" == true ]] && echo "directories" || echo "files")
    local count=0
    local rename_operations=()

    echo "Processing $item_type in: $target_dir"
    [[ -n "$file_filter" ]] && echo "Filtering file extensions: $file_filter"
    echo

    echo "DEBUG: Executing find command:" "${find_cmd[@]}"

    local files=()
    while IFS= read -r -d '' file; do
        files+=("$file")
    done < <("${find_cmd[@]}" 2>/dev/null)

    if [[ ${#files[@]} -eq 0 ]]; then
        echo "No $item_type found matching criteria."
        return
    fi

    for file in "${files[@]}"; do
        local itemname=$(basename "$file")
        local result=$(process_item "$itemname")
        IFS=':' read -r changed old_name new_name <<< "$result"

        if [[ "$changed" == "true" ]]; then
            rename_operations+=("$old_name" "$new_name")
            ((count++))
            printf "%-40s -> %s\n" "$old_name" "$new_name"
        fi
    done

    if [[ $count -eq 0 ]]; then
        echo "No $item_type matched the specified criteria."
        return
    fi

    echo
    echo "$count $item_type would be renamed."

    if [[ "$dry_run" == false ]]; then
        if [[ "$assume_yes" == false ]]; then
            read -p "Proceed with renaming? [y/N] " confirm
            [[ "$confirm" =~ ^[Yy]$ ]] || { echo "Operation canceled."; return; }
        fi

        local success=0
        for ((i=0; i<${#rename_operations[@]}; i+=2)); do
            local old_item="$target_dir/${rename_operations[i]}"
            local new_item="$target_dir/${rename_operations[i+1]}"

            if mv -n "$old_item" "$new_item" 2>/dev/null; then
                ((success++))
            else
                echo "Warning: Failed to rename ${rename_operations[i]}" >&2
            fi
        done

        echo "Successfully renamed $success $item_type."
    fi
}

# Parse command line arguments
parse_arguments() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -d|--dir-mode)
                dir_mode=true
                shift
                ;;
            -f|--filter)
                file_filter="$2"
                shift 2
                ;;
            -p|--prefix)
                current_prefix="$2"
                shift 2
                ;;
            -m|--middle)
                current_middle="$2"
                shift 2
                ;;
            -s|--suffix)
                current_suffix="$2"
                shift 2
                ;;
            -n|--new)
                new_pattern="$2"
                shift 2
                ;;
            --dry-run)
                dry_run=true
                shift
                ;;
            -y|--yes)
                assume_yes=true
                shift
                ;;
            -h|--help)
                show_help
                exit 0
                ;;
            -*)
                echo "Error: Unknown option '$1'" >&2
                show_help >&2
                exit 1
                ;;
            *)
                if [[ -z "$target_dir" ]]; then
                    target_dir="$1"
                    shift
                else
                    echo "Error: Unexpected argument '$1'" >&2
                    show_help >&2
                    exit 1
                fi
                ;;
        esac
    done
}

# Main execution flow
main() {
    init_globals
    parse_arguments "$@"
    validate_arguments

    if [[ "$dry_run" == true ]]; then
        echo "DRY RUN: Showing what would be changed (no items will be modified)"
        echo
    fi

    rename_items
}

# Start the script
main "$@"
